#include <stdio.h>
//字符函数和字符串函数
//1️⃣字符函数  
//①字符分类函数——判断是否为某一类字符
//②字符转换函数
//将字符串中小写字母转成大写字母   见 函数 牛客网BC48
#include <ctype.h>
int main()
{
    int n;
    scanf("%d",&n);        //16
    char arr[n];
    while(gets(arr)!=NULL) //I Am a Chinese.
    {
        int i;
        for(i=0;i<n;i++)
        {
            if(islower(arr[i]))
            {
                arr[i]=toupper(arr[i]);
            }
        }
        printf("%s\n",arr); //I AM A CHINESE.
    }
    return 0;
}
//2️⃣字符串函数
#include <string.h>
//①strlen     vscode 4.c 8.1.c 8.4.c 15.c  vs2022tiaoshi  P60~61 P85 P89 P98
size_t strlen(const char* str)
//参数：str，指针，指向了要统计长度的字符串
//返回值：返回str指向的字符串的长度，返回类型为size_t
int main()
{
    if(strlen("abc")-strlen("abcdef")>0)
    //    3         -      6  =-3  因为strlen返回值为size_t类型，所以-3也要为size_t类型（无符号）
    printf(">\n");   //-3 原码：10000000000000000000000000000011
    else             //   反码：11111111111111111111111111111100
    printf("<=\n");  //   补码：11111111111111111111111111111101   因为是size_t类型，所以这是一个很大的正数的原码
    return 0;
}//输出：>
//可以修改为：
int main()
{
    //if((int)strlen("abc")-(int)strlen("abcdef")>0)
    if(strlen("abc")>strlen("abcdef"))
    printf(">\n");
    else 
    printf("<=\n");
    return 0;
}//输出：<=
//模拟实现：
#include <assert.h>
size_t my_strlen1(char* arr)
{
    assert(arr);
    char *str=arr;  //若形参加const会报警告
    while(*str!='\0')
    {
        str++;
    }
    return str-arr;
}
size_t my_strlen2(const char* arr)
{
    size_t count=0;
    while(*arr)
    {
        count++;
        arr++;
    }
    return count;
}
size_t my_strlen3(const char* arr)
{
    if(*arr)
    return 1+my_strlen3(arr+1);
    else
    return 0;
}
int main()
{
    int n;
    scanf("%d\n",&n);
    char arr[n];
    gets(arr);
    printf("%zu\n",my_strlen1(arr));
    printf("%zu\n",my_strlen2(arr));
    printf("%zu\n",my_strlen3(arr));
    return 0;
}
//②strcpy     vscode 9.c  vs2022tiaoshi  P50 P89 P98 函数
//功能：字符串拷贝，源字符串中的'\0'也拷贝过去
char* strcpy(char* destination, const char* source );
//参数：destination指向目的地空间，source指向源头数据
//返回值：返回目的地空间的起始地址，返回类型为char*
//模拟实现：
#include <assert.h>
char *my_strcpy(char* brr,const char* arr)
{
    assert(brr&&arr);
    char *p=brr;
    while(*brr++=*arr++)
    {
        ;
    }
    return p;
}
int main()
{
    char arr[]="hello world";
    char brr[20]="xxxxxxxxxxxxxxx";
    printf("%s\n",my_strcpy(brr,arr));//输出:hello world
    return 0;
}
//③strcat
//功能：字符串追加，把source指向的源字符串中的所有字符都追加到destination指向的空间中
//源字符串从目标字符串的\0开始追加，会覆盖目标字符串的\0，并且源字符串的\0也会追加过去
char* strcat (char* destination,const char* source);
//参数：destination指向目的地空间，source指向源头数据
//返回值：返回目的地空间的起始地址，返回类型为char*
//代码演示：
#include <string.h>
int main()
{
    char arr1[20] = "hello\0xxxxxxxxx";
//                   hello  world\0xxx
    char arr2[] = " world";
    strcat(arr1,arr2);
    printf("%s\n",arr1);//输出:hello world
    return 0;
}
//模拟实现：
#include <assert.h>
char *my_strcat(char *arr,const char *brr)
{
    assert(arr&&brr);
    char *str=arr;
    while(*arr!='\0')
    {
        arr++;
    }
    while(*arr++=*brr++)
    {
        ;
    }
    return str;
}
int main()
{
    char arr[20]="hello\0xxxxxxx";
    char brr[]=" world";
    printf("%s\n",my_strcat(arr,brr));//输出:hello world
    return 0;
}
//④strcmp    vscode 8.3.c 11.c 12.c  函数 P99
//功能：用来比较str1和str2指向的字符串，从两个字符串的第一个字符开始比较，如果两个字符的ASCII码值相等，就比较下一个字符。直到遇到不相等的两个字符，或者字符串结束。
int strcmp (const char* str1,const char* str2);
//参数：str1指向要比较的第一个字符串，str2指向要比较的第二个字符串
//返回值：标准规定：
//第一个字符串大于第二个字符串，则返回大于0的数字；
//第一个字符串等于第二个字符串，则返回0；
//第一个字符串小于第二个字符串，则返回小于0的数字。
//模拟实现：
//第一种：
int my_strcmp(const char* arr,const char *brr)
{
    assert(arr&&brr);
    while(*arr==*brr)
    {
        if(*arr=='\0')
        return 0;
        arr++;
        brr++;
    }
    if(*arr>*brr)
    return 1;
    else 
    return -1;
}
int main()
{
    char arr[]="abcdef";
    char brr[]="abq";
    if(my_strcmp(arr,brr)>0)
    printf("arr>brr\n");
    else if(my_strcmp(arr,brr)<0)
    printf("arr<brr\n");
    else
    printf("arr=brr\n");
    return 0;
}
//第二种：
#include <assert.h>
int my_strcmp(const char* arr,const char *brr)
{
    assert(arr&&brr);
    while(*arr==*brr)
    {
        if(*arr=='\0')
        return 0;
        arr++;
        brr++;
    }
    return *arr-*brr;
}
int main()
{
    if(my_strcmp("abcdef","ab qllllk")>0)
    printf(">\n");//输出：>     空格字符的ASCII码值为32
    else if(my_strcmp("abcdef","ab qllllk")<0)
    printf("<\n"); 
    else
    printf("=\n");
    return 0;
}
//⑤strncpy
//功能：字符串拷贝，将source指向的字符串拷贝到destination指向的空间中，最多拷贝num个字符。
char* strncpy(char* destination,const char* source,size_t num);
//参数：destination指向目的地空间，source指向源头数据，num从source指向的字符串中最多拷贝的字符个数
//返回值：返回目的地空间的起始地址，返回类型为char*
//代码演示：
int main()
{
    char arr[]="abcdefghi";
    char brr[20]="xxxxxxxxxxx";
    printf("%s\n",strncpy(brr,arr,5));//输出：abcdexxxxxx
    char arr1[]="abc";
    char brr1[20]="xxxxxxxxxxx";//abc\0\0xxx……
    printf("%s\n",strncpy(brr1,arr1,5));//输出：abc
    //这里经过调试会发现补了两个'\0'
    char arr2[]="abc\0defghi";
    char brr2[20]="xxxxxxxxxxx";//abc\0\0xxx……
    printf("%s\n",strncpy(brr2,arr2,5));//输出：abc
    //这里经过调试会发现补了两个'\0'，strncpy函数在arr2中遇到'\0'就认为arr2读完了
    return 0;
}
//⑥strncat
//功能：字符串追加，将source指向的字符串的内容，追加到destination指向的空间，最多追加num个字符。
//源字符串从目标字符串的\0开始追加，会覆盖目标字符串的\0，目标字符串追加末尾只会有一个'\0'。
char* strncat(char* destination,const char* source,size_t num);
//参数：destination指向目的地空间，source指向源头数据，num最多追加的字符的个数
//返回值：返回目的地空间的起始地址，返回类型为char*
//代码演示：
int main()
{
    char arr[]="abc";
    char brr[20]="xxx\0xxxxxxxxxxxxxxxx";//xxxabc\0xxxxxxxxxxxxx
    printf("%s\n",strncat(brr,arr,3));//输出：xxxabc
    //这里经过调试可发现补了一个'\0'，那会不会是"abc"字符串隐藏那个的'\0'？
    char arr1[]="abc";
    char brr1[20]="xxx\0xxxxxxxxxxxxxxxx";//xxxab\0xxxxxxxxxxxxxx
    printf("%s\n",strncat(brr1,arr1,2));//输出：xxxab
    //这里经过调试可发现补了一个'\0'，说明并不是"abc"字符串隐藏那个的'\0'。
    char arr2[]="abc";
    char brr2[20]="xxx\0xxxxxxxxxxxxxxxx";//xxxabc\0xxxxxxxxxxxxx
    printf("%s\n",strncat(brr2,arr2,6));//输出：xxxabc
    //这里经过调试可发现只有一个'\0'，而不是四个'\0'。
    char arr3[]="abcdef";
    char brr3[20]="xxx\0xxxxxxxxxxxxxxxx";//xxxabcdef\0xxxxxxxxxx
    printf("%s\n",strncat(brr3,arr3,6));//输出：xxxabcdef
    return 0;
}
//⑦strncmp
//功能：字符串比较，比较str1和str2指向的两个字符串的内容，最多比较num字符。
int strncmp(const char* str1,const char* str2,size_t num);
//参数：str1指向要比较的第一个字符串，str2指向要比较的第二个字符串，num最多比较的字符个数
//返回值：标准规定：
//第一个字符串大于第二个字符串，则返回大于0的数字；
//第一个字符串等于第二个字符串，则返回0；
//第一个字符串小于第二个字符串，则返回小于0的数字。
//代码演示：
int main()
{
    char arr[]="abcdef";
    char brr[]="abcqw";
    if(strncmp(arr,brr,3)>0)
    printf("arr>brr\n");
    else if(strncmp(arr,brr,3)<0)
    printf("arr<brr\n");
    else
    printf("arr=brr\n");//输出：arr=brr

    if(strncmp(arr,brr,4)>0)
    printf("arr>brr\n");
    else if(strncmp(arr,brr,4)<0)
    printf("arr<brr\n");//输出：arr<brr
    else
    printf("arr=brr\n");
    return 0;
}
//⑧strstr   (string string)
//功能：查找str2指向的字符串在str1指向的字符串中第一次出现的位置。简而言之：在一个字符串中查找子字符串。
char* strstr (const char* str1,const char* str2);
//参数：str1指向了被查找的字符串，str2指向了要查找的字符串
//返回值：
//如果str1指向的字符串中存在str2指向的字符串，那么返回第一次出现位置的指针；
//如果str1指向的字符串中不存在str2指向的字符串，那么返回NULL。
//代码演示：
int main()
{
    char arr[]="hehe abcddef abcdef";
    char brr[]="def";
    if(strstr(arr,brr)!=NULL)
    printf("找到了,%s\n",strstr(arr,brr));//输出：找到了,def abcdef
    else
    printf("找不到\n");
    return 0;
}
//模拟实现：
//第一种：
const char* my_strstr(const char* arr,const char* brr)
{
    const char *str=arr;
    const char *p=brr;
    while(*arr)
    {
        if(*arr!=*brr)
        {
            arr++;
        }
        else
        {
            str=arr;
            while(*brr)
            {
                if(*arr!=*brr)
                break;
                arr++;
                brr++;
            }
            if(*brr=='\0')
            {
                return str;
            }
            else
            {
                brr=p;
                arr=str+1;
            }
        }
    } 
    return NULL; 
}
int main()
{
    char arr[]="heheabcddefabcdef";
    char brr[]="def";
    if(my_strstr(arr,brr))
    printf("找到了，%s\n",my_strstr(arr,brr));
    else
    printf("找不到\n");
    return 0;
}//输出：找到了，defabcdef
//第二种：
char* my_strstr(const char* arr,const char* brr)
{
    char* p=(char*)arr;
    char* s1=NULL;
    char* s2=NULL;
    //特殊情况处理：空字符串
    if(*brr=='\0')
    return (char *)arr;
    while(*p)
    {
        s1=p;
        s2=(char*)brr;
        while(*s1&&*s2&&*s1==*s2)//while(((*s1!=0)||(*s2!=0))&&*s1==*s2)
        {
            s1++;
            s2++;
        }
        if(!*s2)
        return p;
        p++;
    }
    return NULL;
}
int main()
{
    char arr[]="hehe abcddef abcdef";
    char brr[]="def";
    if(my_strstr(arr,brr)!=NULL)
    printf("找到了,%s\n",my_strstr(arr,brr));//输出：找到了,def abcdef
    else
    printf("找不到\n");
    return 0;
}
//strstr函数的实现有多种，可以暴力查找（像上面的两种），也有一种高效一些的算法：KMP。
//⑨strtok
//功能：
//分割字符串：根据delim参数中指定的分隔符，将输入字符串str拆分成多个子字符串。
//修改原始字符串：strtok会直接在原始字符串中插入'\0'终止符，替换分隔符的位置，因此原始字符串会被修改。
char* strtok(char* str,const char* delim);
//参数：
//str：首次调用时传入待分割的字符串，后续调用传入NULL，表示继续分割同一个字符串。(可以使用静态变量保留地址，以便继续分割同一个字符串)
//delim：包含所有可能分隔符的字符串（每个字符均视为独立的分隔符）。
//返回值：成功时返回指向当前子字符串的指针，没有更多子字符串时返回NULL。
//使用步骤：
//(1)首次调用：传入待分割字符串和分隔符。
//(2)后续调用：传入NULL和相同的分隔符，继续分割。
//(3)结束条件：当返回NULL时，表示分割完成。
//代码演示：
int main()
{
    char crr[]="xxd@qq.com";
    char brr[]="@.";
    char arr[50]={0};
    strcpy(arr,crr);
    char *p=NULL;
    //       (1)            (3)           (2)
    for(p=strtok(arr,brr);p!=NULL;p=strtok(NULL,brr))
    {
        printf("%s\n",p);
    }
    //输出：
    //xxd
    //qq
    //com
    //下面只是演示，因为实际应用时子字符串可能不清楚或者很多，所以无法像下面一样一个一个打印
    printf("%s\n",strtok(arr,brr));//xxd\0qq.com      //输出：xxd
    printf("%s\n",strtok(NULL,brr));//xxd\0qq\0com    //输出：qq
    printf("%s\n",strtok(NULL,brr));//xxd\0qq\0com\0  //输出：com
    //printf("%s\n",strtok(NULL,brr)); 这里不会打印，因为没有更多子字符串而返回NULL，空指针不能打印
    return 0;
}
//注意事项：
//破坏性操作：strtok会直接修改原始字符串，将其中的分隔符替换为'\0'。如果需要保留原字符串，应先拷贝一份。(所以代码演示中strcpy(arr,crr);)
//连续分隔符：多个连续的分隔符会被视为单个分隔符，不会返回空字符串。(比如修改为char crr[]="xxd@@@qq.com"; 输出结果也不会变)
//空指针处理：如果输入的str为NULL且没有前序调用，行为未定义。
//⑩strerror
//功能：
//可以通过参数部分的errnum表示错误码，得到对应的错误信息，并且返回这个错误信息字符串首字符的地址。
//只针对标准库中的函数发生错误后设置的错误码的转换。
//⚠️在不同的系统和C语言标准库的实现中都规定了一些错误码，一般是放在<errno.h>这个头文件中说明的。
//C语言程序启动的时候就会使用一个全局的变量errno来记录程序的当前错误码，只不过程序启动的时候errno是0，表示没有错误；
//当我们在使用标准库中的函数的时候发生了某种错误，就会将对应的错误码存放在errno中，而一个错误码的数字是整数，很难理解是什么意思，所以每一个错误码都是有对应的错误信息的。
//strerror函数就可以将错误码对应的错误信息字符串的地址返回。
char* strerror ( int errnum );
//参数：errnum表示错误码，这个错误码一般传递的是errno这个变量的值
//返回值：返回通过错误码得到的错误信息字符串的首字符的地址。
//代码演示：
#include <errno.h>
int main()
{
    int i;
    for(i=0;i<=10;i++)
    {
        printf("%d:%s\n",i,strerror(i));
    }
    return 0;
}
//输出：
//0:No error
//1:Operation not permitted
//2:No such file or directory
//3:No such process
//4:Interrupted function call
//5:Input/output error
//6:No such device or address
//7:Arg list too long
//8:Exec format error
//9:Bad file descriptor
//10:No child processes
#include <errno.h>
int main()
{
    //C语言可以打开文件——fopen函数
    //如果以读的形式打开文件，文件是要必须存在的，如果文件不存在，则打开文件失败，fopen函数就会将错误码放在errno变量中，并返回NULL。
    //FILE *fopen( const char *filename, const char *mode );
    FILE *p=fopen("data.txt","r");//"r"是指以读的形式打开文件
    if(p==NULL)
    {
        printf("错误信息是:%s\n",strerror(errno));//输出：错误信息是:No such file or directory
        return 1;//错误返回
    }
    //关闭文件
    fclose(p);
    p=NULL;
    return 0;
}
//⑪perror  (printf strerror)
void perror( const char *string );
// perror函数相当于一次将上述代码中的第474行完成了，直接将错误信息打印出来。
// perror函数打印完参数部分的字符串后，再打印一个冒号和一个空格，再打印错误信息(它会自动去找error中的错误码)。
#include <errno.h>
int main()
{
    FILE *p=fopen("data.txt","r");
    if(p==NULL)
    {
        perror("错误信息是");//输出：错误信息是: No such file or directory
        return 1;//错误返回
    }
    return 0;
}
//⑫atoi
//功能：将字符串开头的数字字符转换为对应的整数，遇到非数字字符（如字母、符号等）则停止转换。
int atoi(const char *str);
//转换规则：
//跳过前导空白：首先丢弃尽可能多的空格字符，直到找到第一个非空白字符。
//处理符号：若第一个非空白字符是+或-，则作为符号（默认正数）。
//转换数字：依次读取后续数字字符（0-9），转换为整数；遇到非数字字符时立即停止。
//返回值：转换后的整数，若字符串无有效数字则返回0。
//代码演示：
#include <stdlib.h>
int main()
{
    char arr[]="   158dbc";
    int num1=atoi(arr);
    printf("%d\n",num1);//158
    char brr[]="-12hb456";
    int num2=atoi(brr);
    printf("%d\n",num2);//-12
    char crr[]="+-abcde";
    int num3=atoi(crr);
    printf("%d\n",num3);//0
    return 0;
}
//模拟实现：
int my_atoi(const char *str)
{
    //数字字符的ASCII码值是48~57
    const char *p=str;
    while(*p==32)
    {
        p++;
    }
    int sign=1;
    if(*p=='+')
    {
        sign=1;
        p++;
    }
    else if(*p=='-')
    {
        sign=-1;
        p++;
    }
    int n=0;
    while(*p>=48&&*p<=57)
    {
        n=n*10+(*p-48);
        p++;
    }
    return sign*n;
}
int main()
{
    char arr[]="   158dbc";
    int num1=my_atoi(arr);
    printf("%d\n",num1);//158
    char brr[]="-12hb456";
    int num2=my_atoi(brr);
    printf("%d\n",num2);//-12
    char crr[]="+-abcde";
    int num3=my_atoi(crr);
    printf("%d\n",num3);//0
    return 0;
} 